package com.heima.media.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.heima.common.aliyun.GreenImageScan;
import com.heima.common.aliyun.GreenTextScan;
import com.heima.common.minio.MinIOService;
import com.heima.common.util.SensitiveWordUtil;
import com.heima.media.dto.ArticleDto;
import com.heima.media.dto.ContentDto;
import com.heima.media.dto.ImageDto;
import com.heima.media.entity.WmNews;
import com.heima.media.entity.WmSensitive;
import com.heima.media.entity.WmUser;
import com.heima.media.feign.ArticleFeignClient;
import com.heima.media.service.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RQueue;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class NewsScanServiceImpl implements INewsScanService {

    @Autowired
    private GreenImageScan greenImageScan;
    @Autowired
    private GreenTextScan greenTextScan;

    @Autowired
    private MinIOService minIOService;

    @Autowired
    private IChannelService channelService;

    @Autowired
    private ArticleFeignClient articleFeignClient;

    @Autowired
    @Lazy // 懒加载,避免循环注入出错
    private IWmNewsService newsService;

    @Autowired
    private IWmUserService userService;

    @Autowired
    private IWmSensitiveService sensitiveService;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    @Async
    public void scanNow(WmNews wmNews) throws Exception {
        /* 先要组织好提交给阿里审核的内容,分为两个部分,images和text,
         *发起文本审核,构建需要审核的数据并转成JSON格式,tasks,
         *包含bizType(default),scenes(指定检测场景，取值：antispam，表示文本内容检测。),tasks(JSONArray),
         *tasks包含clientInfo,dataId,content(待检测文本)
         */
        // 判断发布时间,如果是4人工审核已通过或者8审核通过,那么直接调用发布
        // 这是延迟发布队列的再入判断
        long time = wmNews.getPublishTime().getTime();// 拿到设置的发布时间
        if (time <= System.currentTimeMillis()) {
            // 已到发布时间,并且状态为审核通过或者人工审核通过
            if (wmNews.getStatus() == 4 || wmNews.getStatus() == 8) {
                publishOrUpdate(wmNews);
                return;
            }
        }
        // 这是此刻发布的处理流程
        // 照理说本方法异步的处理
        log.info(Thread.currentThread().getName() + "||" + wmNews.getTitle() + "--正在送审");
        // 拿到待审核的内容
        Map<String, Object> waitToScan = getWaitToScan(wmNews);
        // 调用审核方法,得到审核结果是通过还是不通过
        // 在送到阿里审核前,先用自定义敏感词典过滤,如果连自定义词典都过不了,那就直接失败
        boolean isPass = scanNewsNow(waitToScan, wmNews);
        if (isPass) {
            // 修改文章状态为通过审核
            wmNews.setStatus(8);  // 当前状态  0 草稿  1 提交（待审核）  2 审核失败  3 人工审核  4 人工审核通过  8 审核通过（待发布）  9 已发布
            // 判断发布时间,已经到发布时间那就直接发布
            if (time <= System.currentTimeMillis()) {
                // 远程调用article提供的接口,发布文章,保存文章内容,拿到返回的文章id
                publishOrUpdate(wmNews);
            } else {
                // 没到发布时间,发送消息到延迟队列,延迟发布
                sendMsgToDelayQueue(wmNews);
            }
        }
        newsService.saveOrUpdate(wmNews);
        log.info(Thread.currentThread().getName() + "||" + "审核结束,状态为:" + wmNews.getStatus());
    }

    private void sendMsgToDelayQueue(WmNews wmNews) {
        RQueue<String> blockingQueue = redissonClient.getBlockingQueue("article-publish");
        RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
        // 发送消息,string消息内容,delayTime延迟时间,timeUnit时间类型,
        long delayTime = wmNews.getPublishTime().getTime() - System.currentTimeMillis();
        delayedQueue.offer(JSON.toJSONString(wmNews), delayTime, TimeUnit.MILLISECONDS);
    }

    private Map<String, Object> getWaitToScan(WmNews wmNews) {
        List<ContentDto> newsContent = JSON.parseArray(wmNews.getContent(), ContentDto.class);
        // 拿到封面图片
        List<ImageDto> imageDtos = JSON.parseArray(wmNews.getImages(), ImageDto.class);
        StringBuilder text = new StringBuilder();
        text.append(wmNews.getTitle());
        text.append(wmNews.getLabels());
        // 遍历内容集合,分别提取图片和文本,图片放入imageDtos,文本直接通过StringBuilder拼接
        for (ContentDto contentDto : newsContent) {
            if (contentDto.getType().equals("text")) {
                text.append(contentDto.getValue());
            } else {
                ImageDto imageDto = new ImageDto();
                imageDto.setId(contentDto.getId());
                imageDto.setUrl(contentDto.getValue());
                imageDtos.add(imageDto);
            }
        }
        // 下载所有图片的字节码放入集合
        List<byte[]> imageByteList = new ArrayList<>();
        if (imageDtos.size() > 0) {
            // 只要图片的url
            List<String> images = imageDtos.stream().map(ImageDto::getUrl).collect(Collectors.toList());
            // 通过minioService下载图片字节流
            for (String image : images) {
                try {
                    InputStream download = minIOService.download(image);
                    byte[] bytes = IOUtils.toByteArray(download);
                    imageByteList.add(bytes);
                } catch (Exception e) {
                    throw new RuntimeException(e.getMessage());
                }
            }
        }
        // 待审核的数据,就在这里了
        Map<String, Object> waitToScan = new HashMap<>();
        waitToScan.put("text", text);
        if (imageDtos.size() > 0) {
            waitToScan.put("images", imageByteList);
        }
        return waitToScan;
    }

    private boolean scanNewsNow(Map<String, Object> waitToScan, WmNews wmNews) throws Exception {
        List<String> label = new ArrayList<>();
        // 调用自定义敏感词库进行过滤
        if (SensitiveWordUtil.dictionaryMap.isEmpty()) {
            LambdaQueryWrapper<WmSensitive> query = new LambdaQueryWrapper<>();
            query.select(WmSensitive::getSensitives);
            // List<String> words = sensitiveService.list(query).stream().map(WmSensitive::getSensitives).collect(Collectors.toList());
            List<String> words = sensitiveService.listObjs(query, Object::toString);
            SensitiveWordUtil.initMap(words);
        }
        Map<String, Integer> matchWords = SensitiveWordUtil.matchWords(waitToScan.get("text").toString());
        if (!matchWords.isEmpty()) {
            String matchWord = String.join(",", matchWords.keySet());
            label.add(matchWord);
            wmNews.setStatus(2);
            wmNews.setReason("敏感词审核失败:" + String.join(",", label));
            return false;
        }
        Map<String, String> textScan = greenTextScan.greenTextScan(waitToScan.get("text").toString());
        Object images = waitToScan.get("images");
        boolean imageScanResult = true;
        if (images != null) {
            Map<String, String> imageScan = greenImageScan.imageScan((List<byte[]>) images);
            imageScanResult = getResult(wmNews, label, imageScan, "image-");
        }
        /* 获取text审核tasks的返回值,results(JSONArray)
         *results包含scene,
         *suggestion(pass：文本正常，可以直接放行。review：文本需要进一步人工审核。block：文本违规，可以直接删除或者限制公开。)
         *label(提示标记)
         *details(JSONArray,命中风险的详细信息)
         */
        // 不知道哪里没审核通过,分别检查
        boolean textScanResult = getResult(wmNews, label, textScan, "text-");
        boolean result = imageScanResult && textScanResult;
        String info = result ? "通过" : "不通过|";
        wmNews.setReason("阿里云审核" + info + String.join(",", label));
        return result;
    }

    private boolean getResult(WmNews wmNews, List<String> label, Map<String, String> ScanResult, String content) {
        String suggestion = ScanResult.get("suggestion");
        boolean result = true;
        switch (suggestion) {
            case "review":
                wmNews.setStatus(3); // 当前状态  0 草稿  1 提交（待审核）  2 审核失败  3 人工审核  4 人工审核通过  8 审核通过（待发布）  9 已发布
                label.add(content + ScanResult.get("label"));
                result = false;
                break;
            case "block":
                wmNews.setStatus(2);
                label.add(content + ScanResult.get("label"));
                result = false;
        }
        return result;
    }

    public void publishOrUpdate(WmNews news) {
        ArticleDto articleDto = new ArticleDto();
        BeanUtils.copyProperties(news, articleDto);
        // 先把信息补一下吧,
        articleDto.setId(news.getArticleId()); // 不管有没有设置一下,没有正好把原来的newsId给干掉
        // 作者信息是没有,要从作者表里面提出来
        articleDto.setChannelName(channelService.getById(news.getChannelId()).getName());
        articleDto.setLayout(news.getType()); // 文章布局  0 无图文章  1 单图文章  2 多图文章,news里面是type
        articleDto.setFlag(0); // 文章标记  0 普通文章  1 热点文章  2 置顶文章  3 精品文章  4 大V 文章
        // 自媒体帐号id也没有,要拿一下
        LambdaQueryWrapper<WmUser> query = new LambdaQueryWrapper<>();
        query.eq(WmUser::getId, news.getUserId());
        WmUser user = userService.getOne(query);
        articleDto.setAuthorId(user.getApAuthorId());
        // 封面图有点不一样,news存的带id是集合,这里只要url
        List<ImageDto> imageDtos = JSON.parseArray(news.getImages(), ImageDto.class);
        List<String> imageList = new ArrayList<>();
        for (ImageDto imageDto : imageDtos) {
            imageList.add(imageDto.getUrl());
        }
        String imageString = String.join(",", imageList);
        articleDto.setImages(imageString);
        articleDto.setLikes(0);
        articleDto.setCollection(0);
        articleDto.setComment(0);
        articleDto.setViews(0);
        articleDto.setSyncStatus(0);
        // apArticle.setOrigin();
        articleDto.setIsComment(true);
        articleDto.setIsForward(true);
        articleDto.setIsDown(false);
        articleDto.setIsDelete(false);
        // apArticle.setStaticUrl(); //我猜这个是通过接口传参数拿到的
        // 远程调用article服务发布
        Long articleId = articleFeignClient.publishOrUpdate(articleDto);
        news.setArticleId(articleId);
        if (articleId != null) {
            news.setStatus(9);
        }
    }
}
